package org.apache.catalina.startup;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Objects;

/**
 * <pre>
 *  Catalina 的引导加载程序。 该应用程序构建了一个类加载器，
 *  用于加载 Catalina 内部类（通过累积在“catalina.home”下的“server”目录中找到的所有 JAR 文件），
 *  并启动容器的常规执行。 这种迂回方法的目的是将 Catalina 内部类（以及它们所依赖的任何其他类，例如 XML 解析器）
 *  保留在系统类路径之外，因此对应用程序级类不可见。
 *
 * @author chentudong
 * @date 2024/2/19 14:44
 * @since 1.0
 */
public class Bootstrap {

  private static final File catalinaBaseFile;
  private static final File catalinaHomeFile;

  /**
   * 以应用类加载器为父类，是tomcat顶层的公用类加载器，
   * 其路径由conf/catalina.properties中的common.loader指定，默认指向${catalina.home}/lib下的包。
   */
  ClassLoader commonLoader = null;

  /**
   * 以Common类加载器为父类，是用于加载Tomcat应用服务器的类加载器， 其路径由server.loader指定，默认为空，此时tomcat使用Common类加载器加载应用服务器。
   */
  ClassLoader catalinaLoader = null;

  /**
   * Daemon reference. Catalina容器（实例）对象指针
   */
  private Object catalinaDaemon = null;

  private Object sharedLoader = null;

  static {
    // 获取用户工作目录
    String userDir = System.getProperty("user.dir");
    File homeFile;
    File f = new File(userDir);
    try {
      homeFile = f.getCanonicalFile();
    } catch (IOException ioe) {
      homeFile = f.getAbsoluteFile();
    }

    catalinaHomeFile = homeFile;
    catalinaBaseFile = catalinaHomeFile;
  }

  /**
   * Obtain the configured base (instance) directory. Note that home and base may be the same (and
   * are by default). If this is not set the value returned by {@link #getCatalinaHomeFile()} will
   * be used.
   *
   * @return the catalina base as a file
   */
  public static File getCatalinaBaseFile() {
    return catalinaBaseFile;
  }

  /**
   * <pre>
   * Obtain the configured home (binary) directory. Note that home and base may be the same (and are
   * by default).
   *
   * {@link Catalina#setParentClassLoader(ClassLoader)}
   * @return the catalina home as a file
   */
  public static File getCatalinaHomeFile() {
    return catalinaHomeFile;
  }

  public static String getCatalinaBase() {
    return catalinaBaseFile.getPath();
  }

  public void init() throws Exception {
    initClassLoaders();
    Thread.currentThread().setContextClassLoader(catalinaLoader);
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();
    String methodName = "setParentClassLoader";
    //参数个数1个，其实就是共享的类加载器
    Method method = startupClass.getMethod(methodName, Class.forName("java.lang.ClassLoader"));
    //使用反射方式调用setParentClassLoader设置为shareLoader
    method.invoke(startupInstance, sharedLoader);
    catalinaDaemon = startupInstance;
  }

  public static void main(String[] args) {
    Bootstrap bootstrap = new Bootstrap();
    try {
      bootstrap.init();
    } catch (Throwable t) {
      System.exit(1);
    }
    try {
      bootstrap.load(args);
    }catch (Throwable t){
      System.exit(1);
    }
  }

  private void initClassLoaders() {
    commonLoader = createClassLoader("common", null);
    if (commonLoader == null) {
      // no config file, default to this loader - we might be in a 'single' env.
      // 假设在catalina.properties配置文件中没有配置common.loader，则commonLoader就是AppClassLoader
      commonLoader = this.getClass().getClassLoader();
    }
    catalinaLoader = createClassLoader("server", commonLoader);
    sharedLoader = createClassLoader("shared", commonLoader);
  }

  private ClassLoader createClassLoader(String name, ClassLoader parent) {
    String value = CatalinaProperties.getProperty(name + ".loader");
    if (Objects.isNull(value) || (value.isEmpty())) {
      return parent;
    }
    return ClassLoaderFactory.createClassLoader(new ArrayList<>(), parent);
  }

  /**
   * 调用{@link Catalina#load()}
   */
  private void load(String[] arguments) throws Exception {
    String methodName = "load";
    Method method = catalinaDaemon.getClass().getMethod(methodName, null);
    method.invoke(catalinaDaemon, null);
  }
}
